﻿using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using DG.Tweening;
using TEngine;
using UnityEngine.Serialization;
using AudioType = TEngine.AudioType;

[RequireComponent(typeof(RectTransform))]
public class ButtonEx : Button, IBeginDragHandler
{
    Tweener tweener;
    Tween delayTweener;
    Tween longPressStartTweener;
    Tweener longPressTriggerTweener;
    RectTransform trans;
    Vector3 defaultScale = Vector3.one;
    Vector3 defaultTweenTransScale = Vector3.one;
    float lastClickTime = 0;

    private bool startedLongPress = false;
    private bool longPressCompleted = false;

    Action<PointerEventData> pointDownCallBack = delegate { };
    Action<PointerEventData> pointUpCallBack = delegate { };
    Action<PointerEventData> pointClickCallBack = delegate { };
    Action<PointerEventData> pointRightClickCallBack = delegate { };
    Action<PointerEventData> pointLongPressCallBack = delegate { };
    Action onDisableClick = delegate { };
    Action<PointerEventData> pointLongPressStartCallBack = delegate { };
    Action pointLongPressCancelCallBack = delegate { };

    [Header("Text Control")] [SerializeField]
    public Text controlText = null;

    [SerializeField] public Color textNormalColor = Color.white;
    [SerializeField] public Color textDisableColor = Color.white;
    [SerializeField] public Shadow textShadow = null;
    [SerializeField] public Color textShadowNormalColor = Color.white;
    [SerializeField] public Color textShadowDisableColor = Color.white;
    [SerializeField] public Shadow textShadow2 = null;
    [SerializeField] public Color textShadow2NormalColor = Color.white;
    [SerializeField] public Color textShadow2DisableColor = Color.white;

    [Header("Animation control")] [SerializeField]
    public bool bounceWhenEnable = false;

    [SerializeField] public bool tweenWhenClick = true;
    [SerializeField] public Transform tweenTrans;

    [SerializeField] public float bounceParam = 0.05f;

    // 每次弹起削减系数 1/3
    [SerializeField] public float bounceWeaken = 3;

    // 弹起时间 0.1秒
    [SerializeField] public float bounceDuration = 0.1f;

    // 弹起次数
    [SerializeField] public int bounceTime = 0;

    [Header("Click control")] [SerializeField]
    public string clickSound = "";

    [SerializeField] public float clickInterval = 0;

    [Header("Long press control")] [SerializeField]
    public bool longPressSwitch = false;

    [SerializeField] public float longPressStartTime = 0.5f;
    [SerializeField] public float longPressTriggerTime = 1f;
    [SerializeField] public bool ignoreClickIfLongPressed = false;
    [SerializeField] public Image longPressProgressImage = null;
    
    [FormerlySerializedAs("m_OnlyLeftClick")] public bool onlyLeftClick = true;

    private static Color GrayColor = new Color(0.4313726f, 0.4117647f, 0.3960784f, 0.7f);
    private static Color TextGrayColor = new Color(0.9098039f, 0.9098039f, 0.9098039f, 1f);
    private Outline[] allOutLines;
    private Color[] allOutLineColors;
    private Shadow[] allShadows;

    private Color[] allShadowColors;

    // private TextMeshProUGUI[] allTexts;
    private Color[] allTextColors;
    private GrayImage[] allGrayImage;

    public bool CanSetDisable = true;

    private List<PressRect> pressRects = new List<PressRect>();
    private Vector3[] cornerBuffer = new Vector3[4];
    private List<RectMask2D> clipperBuffer = new List<RectMask2D>();

    protected override void Awake()
    {
        //此参数固定，Inspector面板调整无效
        base.Awake();
        if (!tweenTrans)
            tweenTrans = transform;
        else
        {
            defaultTweenTransScale = tweenTrans.localScale;
        }

        if (controlText)
        {
            if (null == textShadow)
            {
                textShadow = controlText.GetComponent<Shadow>();
            }

            if (textShadow)
            {
                if (textShadowNormalColor == Color.white)
                {
                    textShadowNormalColor = textShadow.effectColor;
                }

                if (textShadowDisableColor == Color.white)
                {
                    textShadowDisableColor = new Color(1, 1, 1, 0.5f);
                }
            }
        }

        defaultScale = transform.localScale;

        InitBtnDisableComponent();
    }

    protected override void OnDestroy()
    {
        base.OnDestroy();
        pointDownCallBack = null;
        pointUpCallBack = null;
        pointClickCallBack = null;
        pointLongPressCallBack = null;
        pointLongPressStartCallBack = null;
        onDisableClick = null;
        if (tweener != null)
        {
            tweener.Kill();
            tweener = null;
        }

        if (delayTweener != null)
        {
            delayTweener.Kill();
            delayTweener = null;
        }

        ClearLongPressTweener();
        pointLongPressCancelCallBack = null;

        pressRects = null;
        cornerBuffer = null;
        clipperBuffer = null;
    }

    public void SetScale(Vector3 scale)
    {
        defaultScale = scale;
        transform.localScale = scale;
    }

    protected override void OnEnable()
    {
        base.OnEnable();
        transform.localScale = defaultScale;
        if (tweenTrans != null && tweenTrans != transform)
        {
            tweenTrans.localScale = defaultTweenTransScale;
        }

        if (!bounceWhenEnable)
            return;
        if (!trans)
            trans = transform as RectTransform;

        Bounce(trans.GetSiblingIndex() * 0.1f);
    }

    public void InvokeClicked()
    {
        pointClickCallBack?.Invoke(null);
    }

    public void SetDisable(bool setGray, bool respondRelated = true)
    {
        if (!CanSetDisable)
        {
            return;
        }

        SetBtnDisableComponent(setGray);

        if (respondRelated)
        {
            this.enabled = !setGray;
        }
    }

    void InitBtnDisableComponent()
    {
        if (!CanSetDisable)
        {
            return;
        }

        allOutLines = GetComponentsInChildren<Outline>();
        if (allOutLines != null)
        {
            allOutLineColors = new Color[allOutLines.Length];
            for (int i = 0; i < allOutLines.Length; ++i)
            {
                allOutLineColors[i] = allOutLines[i].effectColor;
            }
        }

        allShadows = GetComponentsInChildren<Shadow>();
        if (allShadows != null)
        {
            allShadowColors = new Color[allShadows.Length];
            for (int i = 0; i < allShadows.Length; ++i)
            {
                allShadowColors[i] = allShadows[i].effectColor;
            }
        }

        allGrayImage = GetComponentsInChildren<GrayImage>();
    }

    void SetBtnDisableComponent(bool disable)
    {
        if (!CanSetDisable)
        {
            return;
        }

        if (allOutLines != null)
        {
            for (int i = 0; i < allOutLines.Length; ++i)
            {
                if (allOutLines[i].tag.Equals("NoGray"))
                {
                    continue;
                }

                if (disable)
                {
                    if (allOutLines[i] != null)
                    {
                        allOutLines[i].effectColor = GrayColor;
                    }
                }
                else
                {
                    if (allOutLines[i] != null)
                    {
                        allOutLines[i].effectColor = allOutLineColors[i];
                    }
                }
            }
        }

        if (allShadows != null)
        {
            for (int i = 0; i < allShadows.Length; ++i)
            {
                if (allShadows[i].tag.Equals("NoGray"))
                {
                    continue;
                }

                if (disable)
                {
                    if (allShadows[i] != null)
                    {
                        allShadows[i].effectColor = GrayColor;
                    }
                }
                else
                {
                    if (allShadows[i] != null)
                    {
                        allShadows[i].effectColor = allShadowColors[i];
                    }
                }
            }
        }

        if (allGrayImage != null)
        {
            for (int i = 0; i < allGrayImage.Length; ++i)
            {
                if (allGrayImage[i].tag.Equals("NoGray"))
                {
                    continue;
                }

                if (allGrayImage[i] != null)
                {
                    allGrayImage[i].SetGray(disable);
                }
            }
        }
    }

    void Bounce(float delay)
    {
        if (delayTweener != null)
        {
            delayTweener.Kill();
            delayTweener = null;
        }

        delayTweener = DOVirtual.DelayedCall(delay, () =>
        {
            // 放大
            Tweener(0, bounceParam, bounceDuration);

            delayTweener = DOVirtual.DelayedCall(bounceDuration, () => { Tweener(bounceParam, 0, bounceDuration); });
        });
    }

    void Tweener(float from, float to, float duration, int time = 0)
    {
        if (tweener != null)
        {
            tweener.Kill();
            tweener = null;
        }

        tweener = DOVirtual.Float(from, to, duration,
            (t) => { tweenTrans.localScale = defaultScale - defaultScale * t; });

        if (time > 0)
            tweener.OnComplete(() => { Recursive(from, to, duration, time); });
    }

    void Recursive(float from, float to, float duration, int time)
    {
        if (time <= 0)
            return;
        bool doubleTime = time % 2 == 0;
        if (doubleTime)
        {
            to = to / bounceWeaken;
            from = from / bounceWeaken;
        }

        tweener = DOVirtual.Float(doubleTime ? to : from, doubleTime ? from : to, duration / 2,
            (t) => { tweenTrans.localScale = defaultScale - defaultScale * t; });
        time -= 1;
        if (time > 0)
            tweener.OnComplete(() => { Recursive(from, to, duration, time); });
    }

    protected bool ClearLongPressTweener()
    {
        if (!startedLongPress)
            return false;
        startedLongPress = false;
        if (longPressProgressImage && longPressProgressImage.type == Image.Type.Filled)
            longPressProgressImage.fillAmount = 0;
        if (longPressStartTweener != null)
        {
            longPressStartTweener.Kill();
            longPressStartTweener = null;
        }

        if (longPressTriggerTweener != null)
        {
            longPressTriggerTweener.Kill();
            longPressTriggerTweener = null;
        }

        pointLongPressCancelCallBack?.Invoke();
        return true;
    }

    public void AddClickEvent(UnityAction _action, Action _actionDisable = null)
    {
        pointClickCallBack = data => _action?.Invoke();
        onDisableClick = _actionDisable;
    }

    public void SetRightClickEvent(UnityAction _action)
    {
        pointRightClickCallBack = data => _action?.Invoke();
    }

    public void RemoveAllClickEvents()
    {
        pointRightClickCallBack = null;
        pointClickCallBack = null;
    }

    public void AddPointerDownUpEvent(Action<PointerEventData> action, Action<PointerEventData> action2 = null)
    {
        pointDownCallBack = action;
        pointUpCallBack = action2;
    }

    public void AddPointerClickEvent(Action<PointerEventData> action)
    {
        pointClickCallBack = action;
    }

    public void AddLongPressEvent(Action<PointerEventData> action)
    {
        pointLongPressCallBack = action;
    }

    public void AddLongPressStartEvent(Action<PointerEventData> action)
    {
        pointLongPressStartCallBack = action;
    }

    public void AddLongPressCancelEvent(Action action)
    {
        pointLongPressCancelCallBack = action;
    }

    private void InnerOnPointerClick(PointerEventData eventData)
    {
        if (longPressCompleted && ignoreClickIfLongPressed)
        {
            ClearLongPressTweener();
            longPressCompleted = false;
            return;
        }
        
        if (eventData.currentInputModule.input.touchCount > 1)
        {
            return;
        }
        

        if (clickInterval <= Time.time - lastClickTime)
        {
            lastClickTime = Time.time;
            
            if (onlyLeftClick && eventData.button == PointerEventData.InputButton.Right)
            {
                pointRightClickCallBack?.Invoke(eventData);
            }
            else
            {
                onClick.Invoke();
                pointClickCallBack?.Invoke(eventData);
            }
            if (!interactable && null != onDisableClick)
            {
                onDisableClick();
            }
        }

        if (string.IsNullOrEmpty(clickSound))
            return;
        clickSound = clickSound.Replace("\r", "");
        clickSound = clickSound.Replace(" ", "");
        var audioCfg= ConfigSystem.Instance.Tables.TbAudio.GetOrDefault(clickSound);
        if(audioCfg != null)
            GameModule.Get<AudioModule>().Play(AudioType.UISound, audioCfg.Path);
    }

    public override void OnPointerDown(PointerEventData eventData)
    {
        base.OnPointerDown(eventData);

        if (eventData.currentInputModule.input.touchCount > 1)
        {
            return;
        }
        
        pointDownCallBack?.Invoke(eventData);
        ClearLongPressTweener();
        longPressCompleted = false;
        if (!startedLongPress && longPressSwitch)
        {
            startedLongPress = true;
            longPressStartTweener = DOVirtual.DelayedCall(longPressStartTime, () =>
            {
                longPressTriggerTweener = DOVirtual.Float(0, 1, longPressTriggerTime, (t) =>
                {
                    if (longPressProgressImage && longPressProgressImage.type == Image.Type.Filled)
                        longPressProgressImage.fillAmount = t;
                });
                longPressTriggerTweener.OnStart(() => { pointLongPressStartCallBack?.Invoke(eventData); });
                longPressTriggerTweener.OnComplete(() =>
                {
                    pointLongPressCallBack?.Invoke(eventData);
                    ClearLongPressTweener();
                    longPressCompleted = true;
                });
            });
        }

        if (!interactable)
            return;
        
        pressRects.Clear();
        clipperBuffer.Clear();
        var graphics = GetComponentsInChildren<Graphic>();
        foreach (var graphic in graphics)
        {
            if (!graphic.raycastTarget)
                continue;
            graphic.rectTransform.GetWorldCorners(cornerBuffer);
            var rect = Corners2Rect(cornerBuffer, graphic.canvas.rootCanvas);
            if (graphic is MaskableGraphic maskableGraphic)
            {
                var canvasRenderer = graphic.GetComponent<CanvasRenderer>();
                if (canvasRenderer.hasRectClipping)
                {
                    var rectMask = MaskUtilities.GetRectMaskForClippable(maskableGraphic);
                    if (rectMask != null)
                    {
                        MaskUtilities.GetRectMasksForClip(rectMask, clipperBuffer);
                        var andClipWorldRect = Clipping.FindCullAndClipWorldRect(clipperBuffer, out var validRect);
                        if (!validRect || !RectIntersects(rect, andClipWorldRect, out rect))
                            continue;
                    }
                }
            }

            pressRects.Add(new PressRect {canvas = graphic.canvas.rootCanvas, rect = rect});
        }

        if (tweenWhenClick)
            Tweener(0, bounceParam, bounceDuration);
    }

    public override void OnPointerUp(PointerEventData eventData)
    {
        base.OnPointerUp(eventData);

        ClearLongPressTweener();
        
        if (eventData.currentInputModule.input.touchCount > 1)
        {
            return;
        }

        pointUpCallBack?.Invoke(eventData);
        if (!interactable)
            return;
        if (tweenWhenClick)
            Tweener(bounceParam, 0, bounceDuration, bounceTime);
        if (!eventData.dragging || eventData.pointerPress == eventData.pointerDrag)
        {
            foreach (var pressRect in pressRects)
            {
                if (RectTransformUtility.ScreenPointToLocalPointInRectangle(pressRect.canvas.transform as RectTransform,
                        eventData.position, pressRect.canvas.worldCamera, out var localPos) &&
                    pressRect.rect.Contains(localPos))
                {
                    InnerOnPointerClick(eventData);
                    break;
                }
            }
        }
    }

    public virtual void OnBeginDrag(PointerEventData eventData)
    {
        ClearLongPressTweener();
    }

    protected override void DoStateTransition(SelectionState state, bool instant)
    {
        base.DoStateTransition(state, instant);
        if (!controlText)
            return;
        if (state == SelectionState.Normal)
        {
            controlText.color = textNormalColor;
            if (textShadow)
            {
                textShadow.effectColor = textShadowNormalColor;
            }

            if (textShadow2)
            {
                textShadow2.effectColor = textShadow2NormalColor;
            }
        }

        else if (state == SelectionState.Disabled)
        {
            controlText.color = textDisableColor;
            if (textShadow)
            {
                textShadow.effectColor = textShadowDisableColor;
            }

            if (textShadow2)
            {
                textShadow2.effectColor = textShadow2DisableColor;
            }
        }
    }

    private Rect Corners2Rect(Vector3[] points, Canvas canvas)
    {
        for (var index = 0; index < points.Length; ++index)
            points[index] = canvas.transform.InverseTransformPoint(points[index]);
        var min = points[0];
        var max = points[0];
        for (var i = points.Length - 1; i >= 1; i--)
        {
            var point = points[i];
            min.x = Mathf.Min(min.x, point.x);
            min.y = Mathf.Min(min.y, point.y);
            max.x = Mathf.Max(max.x, point.x);
            max.y = Mathf.Max(max.y, point.y);
        }

        return Rect.MinMaxRect(min.x, min.y, max.x, max.y);
    }

    private bool RectIntersects(Rect r1, Rect r2, out Rect area)
    {
        area = new Rect();
        if (!r2.Overlaps(r1)) return false;
        float x1 = Mathf.Min(r1.xMax, r2.xMax);
        float x2 = Mathf.Max(r1.xMin, r2.xMin);
        float y1 = Mathf.Min(r1.yMax, r2.yMax);
        float y2 = Mathf.Max(r1.yMin, r2.yMin);
        area.x = Mathf.Min(x1, x2);
        area.y = Mathf.Min(y1, y2);
        area.width = Mathf.Max(0.0f, x1 - x2);
        area.height = Mathf.Max(0.0f, y1 - y2);
        return true;
    }

    private class PressRect
    {
        public Canvas canvas;
        public Rect rect;
    }
}